﻿using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Security.Principal;

namespace SysI
{
    class Program
    {
        static void Main(string[] args)
        {
            var options = new Options(args);

            if (options.help)
            {
                ShowTools("http://live.sysinternals.com/", options.terse);
            }
            else if (options.allFiles)
            {
                GetAllTools("http://live.sysinternals.com/", options.forceDownload);
            }
            else
            {
                GetAndExecuteTool(options.tool, options.newArgs, options.forceDownload);
            }

            return;
        }

        private static void GetAndExecuteTool(string fileName, string newArgs, bool forceDownload)
        {
            if (!extensions.EndsWith(fileName, ".exe", ".txt", ".sys", ".chm", ".scr", ".hlp", ".cnt", ".url", ".html"))
            {
                fileName += ".exe";
            }

            if (forceDownload || !File.Exists(fileName))
            {
                using (var c = new WebClient())
                {
                    c.Proxy = WebRequest.DefaultWebProxy;
                    c.Proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;

                    try
                    {
                        c.DownloadFile("http://live.sysinternals.com/" + fileName, fileName);
                    }
                    catch (WebException ex)
                    {
                        Console.WriteLine("WebException: " + ex.Message);
                        return;
                    }
                }
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.WriteLine("(Tool is already downloaded.)");
                Console.WriteLine("To force re-download, add a '!' to the end of the tool name.");
                Console.WriteLine("e.g. 'sysI " + fileName + "!'");
                Console.ResetColor();
            }


            try
            {
                using (var proc = new Process())
                {
                    proc.EnableRaisingEvents = false;

                    proc.StartInfo.FileName = fileName;
                    proc.StartInfo.Arguments = newArgs;
                    proc.StartInfo.UseShellExecute = false;                
                    proc.StartInfo.CreateNoWindow = false;
                    proc.Start();
                }
            }
            catch (Win32Exception)
            {
                if (IsAdmin())
                {
                    throw;
                } else
                {
                    //Elevation required?
                    //try it again...
                    Console.ForegroundColor = ConsoleColor.DarkYellow;
                    Console.WriteLine("Elevation required... Trying again as admin");
                    Console.ResetColor();
                    using (var proc = new Process())
                    {
                        proc.EnableRaisingEvents = false;

                        proc.StartInfo.FileName = fileName;
                        proc.StartInfo.Arguments = newArgs;
                        proc.StartInfo.UseShellExecute = true;
                        proc.StartInfo.CreateNoWindow = false;
                        proc.StartInfo.Verb = "runas";
                        proc.Start();
                    }
                }

            }
        }

        private static void GetAllTools(string url, bool forceDownload)
        {
            //Or... Get http://live.sysinternals.com/Files/SysinternalsSuite.zip
            //and unzip.

            var links = GetSysInternalsLinks(url);

            Console.WriteLine("Getting all files...");

            using (var c = new WebClient())
            {
                c.Proxy = WebRequest.DefaultWebProxy;
                c.Proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;

                foreach (Match link in links)
                {
                    var fileName = link.Groups[2].Value;

                    //No file extension? Then it's not a file, it's a dir
                    if (!fileName.Contains(".")) continue;

                    Console.WriteLine("Getting " + fileName);

                    if (forceDownload || !File.Exists(fileName))
                    {
                        try
                        {
                            c.DownloadFile("http://live.sysinternals.com/" + fileName, fileName);
                        }
                        catch (WebException ex)
                        {
                            Console.WriteLine("WebException: " + ex.Message);
                        }
                    }
                }
            }
        }

        private static void ShowTools(string url, bool terse)
        {
            //LADIES DON'T KNOW BOUT MY CONSOLE COLOR
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.Write("SysI.exe ");
            Console.ForegroundColor = ConsoleColor.DarkGray;
            Console.WriteLine("(Pronounced 'Sissy'.)");
            Console.ResetColor();
            Console.WriteLine("Lists, gets, and executes tools from Live.SysInternals.com");
            Console.Write("Usage: ");
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("SYSI * | tool [params] ");
            Console.Write("\t*\t");
            Console.ResetColor();
            Console.WriteLine(": Download all tools");
            Console.ForegroundColor = ConsoleColor.White;
            Console.Write("\ttool\t");
            Console.ResetColor();
            Console.WriteLine(": is one of the following:\r\n");

            var links = GetSysInternalsLinks(url);

            foreach (Match link in links)
            {
                string fileName = link.Groups[2].Value;

                //No file extension? Then it's not a file, it's a dir
                if (!fileName.Contains(".")) continue;

                if (fileName.EndsWith(".exe"))
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                }
                else if (extensions.EndsWith(fileName,".hlp",".chm"))
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                }
                else
                {
                    Console.ResetColor();
                }

                if (terse)
                {
                    Console.Write(fileName);
                    Console.Write(" ");
                }
                else
                {
                    Console.WriteLine(fileName);
                }
            }

            Console.ResetColor();
        }

        private static MatchCollection GetSysInternalsLinks(string url)
        {
            string sysHtml;
            using (var c = new WebClient())
            {
                c.Proxy = WebRequest.DefaultWebProxy;
                c.Proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
                sysHtml = c.DownloadString(url);
            }

            //CTHULU I CHALLENGE THEE!
            //WITH MY REGEX I PARSE YOUR HTML!
            //e.g <A HREF="/DISKMON.HLP">DISKMON.HLP</A>
            var linkFinder = new Regex("<A HREF=\"(/[^\"]+)\">([^<]+)</A>");
            return linkFinder.Matches(sysHtml);
        }
        static internal bool IsAdmin()
        {
            WindowsIdentity id = WindowsIdentity.GetCurrent();
            WindowsPrincipal p = new WindowsPrincipal(id);
            return p.IsInRole(WindowsBuiltInRole.Administrator);
        }
    }

    class Options
    {
        public bool help { get; private set; }
        public bool terse { get; private set; }
        public bool allFiles { get; private set; }
        public bool forceDownload { get; private set; }
        public string newArgs { get; private set; }
        public string tool { get; private set; }

        public Options(string[] args)
        {
            if (args.Length == 0 ||
                extensions.In(args[0].Trim(), "?","-?","/?","help","-help","--help", "/help"))
            { 
                help = true;
                terse = (args.Length == 1);
            }
            else if (args.Length == 1 &&
                extensions.In(args[0].Trim(), "*", "*!", "-*", "-*!", "--*", "--*!", "/*", "/*!"))
                     
            {
                allFiles = true;
                forceDownload = args[0].Contains("!");
            }
            else
            {
                tool = args[0];
                //Avoid use of 'skip(1)' as it depends on Linq
                //newArgs = String.Join(" ", args.Skip(1) as string[] ?? new string[1] { "" });

                if (args.Length > 1)
                {
                    var newArgsArray = new String[args.Length - 1];
                    Array.Copy(args, 1, newArgsArray, 0, args.Length - 1);
                    newArgs = String.Join(" ", newArgsArray);
                }
                else
                {
                    newArgs = "";
                }
                forceDownload = false;

                if (tool.EndsWith("!"))
                {
                    forceDownload = true;
                    tool = tool.TrimEnd('!');
                }
            }
        }

    }

    public static class extensions
    {
        public static bool In(/*this*/ string that, params string[] strings)
        {
            foreach (var s in strings)
            {
                if (s.ToLowerInvariant()  == that) return true;
            }

            return false;
        }

        public static bool EndsWith(/*this*/ string that, params string[] strings)
        {
            foreach (var s in strings)
            {
                if (that.EndsWith(s, StringComparison.InvariantCultureIgnoreCase)) return true;
            }

            return false;
        }


        public static bool Like(/*this*/ string that, params string[] strings)
        {
            foreach (var s in strings)
            {
                if (Like(that, s)) return true;
            }

            return false;
        }

        public static bool Like(/*this*/ string that, string stringy)
        {
            //
            var regex = new Regex("^" + that.Replace(".", "\\.").Replace("?", ".{1,1}").Replace("*", ".*") + "$");
            return false;
        }

    }
}